using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Silky.Core.Exceptions;
using Silky.Core.Extensions;
using Silky.Core.Serialization;
using Silky.Http.Core.Configuration;
using Silky.Rpc.Extensions;
using Silky.Rpc.Runtime.Server;

namespace Silky.Http.Core.Middlewares
{
    public class WrapperResponseMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly GatewayOptions _gatewayOptions;
        private readonly ISerializer _serializer;
        private readonly ILogger<WrapperResponseMiddleware> _logger;
        private readonly List<Regex> _ignoreWrapperRegexPatterns;

        public WrapperResponseMiddleware(RequestDelegate next, IOptionsMonitor<GatewayOptions> gatewayOptions,
            ISerializer serializer, ILogger<WrapperResponseMiddleware> logger)
        {
            _next = next;
            _serializer = serializer;
            _logger = logger;
            _gatewayOptions = gatewayOptions.CurrentValue;

            // 预编译 Regex 提升性能
            _ignoreWrapperRegexPatterns = _gatewayOptions.IgnoreWrapperPathPatterns
                .Select(pattern => new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase))
                .ToList();
        }

        public async Task Invoke(HttpContext context)
        {
            _logger.LogInformation("WrapperResponseMiddleware invoked for {Method} {Path}", context.Request.Method,
                context.Request.Path);

            if (IsIgnoreWrapperPath(context.Request.Path, context.Request.Method))
            {
                _logger.LogDebug(
                    "Path {Path} with method {Method} matched IgnoreWrapperPathPatterns, bypassing wrapper.",
                    context.Request.Path, context.Request.Method);
                await _next(context);
                return;
            }

            var serviceEntry = context.GetServiceEntry();
            var serviceEntryDescriptor = context.GetServiceEntryDescriptor();

            if (NeedPassthrough(serviceEntry, serviceEntryDescriptor))
            {
                _logger.LogDebug("ServiceEntry or ServiceEntryDescriptor indicates passthrough for {Path}.",
                    context.Request.Path);
                await _next(context);
                return;
            }

            var originalBodyStream = context.Response.Body;
            await using var bodyStream = new MemoryStream();
            try
            {
                context.Response.Body = bodyStream;
                await _next(context);
                context.Response.Body = originalBodyStream;

                var bodyAsText = await FormatResponse(bodyStream);
                _logger.LogTrace("Response body captured for {Path}, wrapping response.", context.Request.Path);

                await HandleResponseAsync(context, bodyAsText, context.Response.StatusCode);
            }
            catch (Exception ex)
            {
                context.Response.Body = originalBodyStream;
                _logger.LogError(ex, "Exception caught in WrapperResponseMiddleware for {Path}: {Message}",
                    context.Request.Path, ex.Message);
                await HandleExceptionAsync(context, ex);
            }
        }

        private bool IsIgnoreWrapperPath(string path, string method)
        {
            return _ignoreWrapperRegexPatterns.Any(regex => regex.IsMatch(path))
                   && method.Equals("GET", StringComparison.OrdinalIgnoreCase);
        }

        private bool NeedPassthrough(ServiceEntry serviceEntry, ServiceEntryDescriptor serviceEntryDescriptor)
        {
            return (serviceEntry != null && (typeof(IActionResult).IsAssignableFrom(serviceEntry.ReturnType)
                                             || serviceEntry.ServiceEntryDescriptor.IsUnWrapperResult()))
                   || (serviceEntryDescriptor != null && (serviceEntryDescriptor.IsActionResult
                                                          || serviceEntryDescriptor.IsUnWrapperResult()));
        }

        private async Task HandleResponseAsync(HttpContext context, string body, int httpStatusCode)
        {
            context.Response.ContentType = context.GetResponseContentType(_gatewayOptions);

            var status = context.Response.GetResultCode(httpStatusCode);
            var responseResultDto = new ResponseResultDto
            {
                Status = (int)status,
                Code = status.ToString()
            };

            if (status == StatusCode.Success)
            {
                try
                {
                    responseResultDto.Result = body.IsValidJson() ? _serializer.Deserialize<dynamic>(body) : body;
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex,
                        "Failed to deserialize response body as JSON for {Path}, returning as string.",
                        context.Request.Path);
                    responseResultDto.Result = body;
                }
            }
            else
            {
                await FillErrorResponse(context, responseResultDto, status);
            }

            var jsonString = _serializer.Serialize(responseResultDto);
            context.Response.StatusCode = (int)status;

            _logger.LogTrace("Wrapped response for {Path} with status {Status}.", context.Request.Path, status);
            await context.Response.WriteAsync(jsonString);
        }

        private async Task FillErrorResponse(HttpContext context, ResponseResultDto response, StatusCode status)
        {
            var exceptionFeature = context.Features.Get<ExceptionHandlerFeature>();
            if (exceptionFeature?.Error != null)
            {
                var ex = exceptionFeature.Error;
                var exceptionStatusCode = ex.GetExceptionStatusCode();
                response.Status = (int)exceptionStatusCode;
                response.Code = exceptionStatusCode.ToString();
                response.ErrorMessage = ex.GetExceptionMessage();

                if (ex is ValidationException validationException)
                {
                    response.ValidErrors = validationException.GetValidateErrors();
                }

                _logger.LogDebug("Filled error response from ExceptionHandlerFeature for {Path}: {Message}",
                    context.Request.Path, ex.Message);
            }

            if (response.ErrorMessage.IsNullOrEmpty())
            {
                response.ErrorMessage = status.GetDisplay();
            }

            await Task.CompletedTask;
        }

        private async Task<string> FormatResponse(Stream bodyStream)
        {
            bodyStream.Seek(0, SeekOrigin.Begin);
            using var streamReader = new StreamReader(bodyStream);
            var plainBodyText = await streamReader.ReadToEndAsync();
            bodyStream.Seek(0, SeekOrigin.Begin);
            return plainBodyText;
        }

        private async Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            var statusCode = exception.GetExceptionStatusCode();
            var status = exception.GetExceptionStatus();

            var responseResultDto = new ResponseResultDto
            {
                Status = status,
                Code = statusCode.ToString(),
                ErrorMessage = exception.Message
            };

            if (exception is IHasValidationErrors hasValidationErrors)
            {
                responseResultDto.ValidErrors = hasValidationErrors.GetValidateErrors();
            }

            context.Response.ContentType = context.GetResponseContentType(_gatewayOptions);
            context.Response.StatusCode = (int)statusCode;

            var responseResultData = _serializer.Serialize(responseResultDto);

            _logger.LogError(exception,
                "Handled exception for {Path}, returning error response with status {StatusCode}.",
                context.Request.Path, statusCode);
            await context.Response.WriteAsync(responseResultData);
        }
    }
}